﻿/*****************************************************************************\
 *             University of Illinois/NCSA Open Source License
 *
 *
 *      Copyright © 2012,2013, The Board of Trustees of the University of Illinois. All rights reserved.
 *
 *      Developed by: Rakesh Bobba, Derek Dagit, Erich Heine, Denis Kholine,
 *                    Klara Nahrstedt, David Rodgers,   William H. Sanders, Tim Yardley
 *
 *      Information Trust Institute/Coordinated Science Laboratory
 * 
 *      University of Illinois
 *      
 *      The Board of Trustees of the University of Illinois
 *      109 Coble Hall
 *      801 South Wright Street
 *      Champaign, IL 61820-6242
 *
 *      File: AdapterBase.cs
 *
 *      Authors: Erich Heine, David Rodgers, Denis Kholine, Derek Dagit,
 *      William H. Sanders, Klara Nahrstedt, Rakesh Bobba, Tim Yardley
 *      Creation Date: 2012
 *
 *      Copyright (c) 2012 William H. Sanders, Klara Nahrstedt, Erich Heine,
 *      Rakesh Bobba, Tim Yardley,
 *      and The Board of Trustees of the University of Illinois
 *      All rights reserved.
 * 
 *      THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 *      LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN
 *      NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 *      SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
 *
 \*****************************************************************************/

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using GSF;
using SIEGate.Core.Configurator;
using SIEGate.Core.Stats;

namespace SIEGate.Core
{
    /// <summary>
    /// The base class for all SIEGate-style adapters.
    /// 
    /// An adapter is the main data processing unit for SIEGate. It takes measurements as input, and outputs different measurements as a result.
    /// The output measurements must have different StreamIDs than the input measurements. What exactly an Adapter does is not defined other than this,
    /// so basically it's sole job is to do something with measurements.
    /// 
    /// In the larger context of SIEGate, an adapter will exist within an AdapterContainer, which is defined within SIEGate itself. The AdapterContainer
    /// takes care of interactions with the Measurement passing meachanism (The Data Bus or Measurement Bus), as well as handling threading issues. SIEGate
    /// in general takes care of configuring adapters, and networking via other mechanisms, as well as handling system stuff. This means Adapters in SIEGate
    /// can be made simply, with a focus on processing the data as well and efficiently as possible. Further, since the SIEGate handles all message routing, and
    /// proides mechanisms for creating multi-part processing chains, the Adapters can be single purpose. Adapters should in fact only do one thing, and let SIEGate
    /// handle the creation and setup of a processing pipeline (e.g., rather than having each adapter do it's own concentration, just configure a 
    /// concentrating adapter upstream from the processing adapter). This format was inspired by SOA and the Unix philosophy, and allows for very creative
    /// and powerful processing systems made from simple adapters.
    /// 
    /// Several things are guaranteed to be taken care of by the SIEGate for the Adapter writer. These include:
    /// 
    /// * measurement ordering - measurements will be presented to the adapter in the order received, or if a reordering adapter is placed upstream, in some defined order
    ///   (e.g. strict time ordering)
    /// * thread safety -  for the data processing code path code will be run in a single thread, serially. This means critical path code need not bother with
    ///   locks or other threading mechanisms. (Critical path being HandleMeasurement and HandleMeasurements code, and methods called by them). Code run from
    ///   notification callbacks may be called in a multithreaded way however, so this needs to be considered when dealing with Intra-SIEGate messaging.
    /// * Instantiation of the configuration and Adapter object. These are handled during system (re)initialization.
    /// 
    /// </summary>
    public abstract class AdapterBase : IAdapter
    {
        #region [ Members ]

        // Nested Types

        /// <summary>
        /// Base class for AdapterInfo. 
        /// 
        /// AdapterInfo objects are used for storing configuration information specifically for Adapters.
        /// For more info on howconfiguration info objects work, <see cref="ConfigInfo"/>
        /// 
        /// AdapterInfo objects are used by <see cref="AdapterBase"/> (and subclasses), and <see cref="ConfigGraph"/>
        /// to set up the runtime for SIEGate.
        /// </summary>
        public abstract class AdapterInfoBase : ConfigInfo, IAdapterInfo, INotificationTarget
        {
            #region [ Members ]

            // Events
            /// <summary>
            /// Logging hook for the config, and it's associated class.
            /// </summary>
            public event EventHandler<EventArgs<string>> Log;

            // Fields
            /// <summary>
            /// Setting defaults for this config object. Used by <see cref="DefaultGet"/> method for 
            /// allowing default values in a config.
            /// </summary>
            private readonly Dictionary<string, string> m_defaultSettings = new Dictionary<string, string>();
            
            private Type m_adapterType;

            #endregion

            #region [ Properties ]

            /// <summary>
            /// Settings dictionary, generated from the ConfigurationString representing and Adapter Instance's settings.
            /// 
            /// If anything is explicityly set in config, this overrides the default values, when used in conjunction with
            /// <see cref="DefaultGet"/>
            /// </summary>
            public Dictionary<string, string> Settings
            {
                get;
                set;
            }

            /// <summary>
            /// AdapterType is used by instantiator for the purpose of creating the actual instance 
            /// of the Adapter after configuration is done. This is set and used outside of the Info
            /// class in stuff that is SIEGate only, so no need to make it anything but internal.
            /// </summary>
            public override Type RuntimeType
            {
                //Todo: make this internal, fix Activator stuff
                get
                {
                    return m_adapterType;
                }
                set
                {
                    if (typeof(IAdapter).IsAssignableFrom(value))
                    {
                        m_adapterType = value;
                    }
                    else
                    {
                        throw new NotSupportedException("Must assign a Type that implements IAdapter to AdapterType");
                    }
                }
            }

            // HACK: Instance pointer, just because it helps wierdnesses during refactoring hopefully goes away. more hopefully becomes internal
            /// <summary>
            /// The instancce of an Adapter Associated with this Configuration set. This should not be relied on, and will be removed in a future
            /// version.
            /// </summary>
            public IAdapter MyInstance
            {
                get;
                set;
            }

            /// <summary>
            /// Timeout value for how long an Adapter's "Initialize()" method can take. This is a holdover from GSF.TimeSeries and should probably
            /// go away soon
            /// </summary>
            public int InitializationTimeout
            {
                get;
                set;
            }


            #endregion

            #region [ Methods ]

            /// <summary>
            /// Handler for Logging.
            /// </summary>
            /// <param name="format"></param>
            /// <param name="args"></param>
            public void OnLog(string format, params object[] args)
            {
                if (Log != null)
                    Log(this, new EventArgs<string>(String.Format(format, args)));
            }

            /// <summary>
            /// Update the configuration for the associated info object. Sets the Settings dict, but 
            /// may be used for computed updates as well.
            /// </summary>
            /// <seealso cref="ExtractConfiguration"/>
            /// <param name="configInfo">Dictionary of settings for this object.</param>
            public override void Update(Dictionary<string, string> configInfo)
            {
                Settings = configInfo;
            }

            /// <summary>
            /// ExtractConfiguration is used to take the format of the configuration (currently a DataSet) and 
            /// uses the provided information to set up the adapter. As the configuration system evolves, this method 
            /// will also evolve. The goal is to have everything tied to a specific configuration system end up here.
            /// </summary>
            /// <param name="configurationTables">DataSet of the current configuration database. This param will evolve with the config system.</param>
            public override void ExtractConfiguration(DataSet configurationTables)
            {
                string setting;

                if (Settings.TryGetValue("incomingStreamIDs", out setting))
                    IncomingStreamIDs = GsfHandler.ParseStreamIDs(configurationTables, setting);

                if (Settings.TryGetValue("outgoingStreamIDs", out setting))
                    OutgoingStreamIDs = GsfHandler.ParseStreamIDs(configurationTables, setting);
            }

            /// <summary>
            /// Updates the default settings dictionary in AdapterBase. Must be used to set the defaults for
            /// any and all classes that extend AdapterInfoBase. This method is used to reduce the amount of reflection and chained
            /// calls within SetDefaults, and to avoid the need for each extension class to implement thier own SetDefaults.
            /// Note, this overwrites any already existing settings, so the Instances of AdapterInfo should call this AFTER the
            /// base class constructor is called.
            /// </summary>
            /// <param name="defaults">default settings.</param>
            public void SetDefaults(Dictionary<string, string> defaults)
            {
                foreach (string key in defaults.Keys)
                {
                    m_defaultSettings[key] = defaults[key];
                }
            }

            /// <summary>
            /// Gets a setting from the Settings Dictionary. If that Setting is not present, it searches the
            /// defaults set by the object in it's consturctor. If no there is not a default setting it returns 
            /// a null. This allows the use of this method to be flexible, but puts the responsibility of handling 
            /// a no default case on the final implementor.
            /// </summary>
            /// <param name="key"></param>
            /// <returns></returns>
            public string DefaultGet(string key)
            {
                string res;
                if (!Settings.TryGetValue(key, out res))
                    m_defaultSettings.TryGetValue(key, out res);
                return res;
            }

            #endregion
        }

        // Fields
        /// <summary>
        /// The stats cluster for this Adapter object. Used for tracking this Adapter's individual statistics. Note: the stats system will
        /// handle aggregation, so just record stats for your Adapter.
        /// </summary>
        protected Cluster m_statsCluster;
        private Counter m_nrecv;
        private Counter m_nsent;
        #endregion

        #region [ Constructors ]

        /// <summary>
        /// Constructor for an AdapterBase instance. The base handles a few things for the end implementor:
        /// 
        /// * It creates a a stats cluster, for easy access to the stats system
        /// * It associates the AdapterInfo object with and AdapterBase instance.
        /// * It sets up a notification receiver for this Adapter.
        /// </summary>
        /// <param name="info"></param>
        protected AdapterBase(IAdapterInfo info)
        {
            Info = info;
            ((AdapterInfoBase)Info).MyInstance = this;
            Receiver = Info.Receiver;
            Info.Receiver = null;

            m_statsCluster = Stats.Stats.StatDispatcher.RegisterCluster(info.ID);
            m_statsCluster.RegisterDuration(Info.Name, Stats.StringConstants.Uptime);
            m_nsent = m_statsCluster.RegisterCounter(Info.Name, Stats.StringConstants.EmittedMeasurements);
            m_nrecv = m_statsCluster.RegisterCounter(Info.Name, Stats.StringConstants.ReceivedMeasurements);
        }

        #endregion

        #region [ Properties ]

        /// <summary>
        /// This instance's configuration object. Info should be considered the canonical source of configuration
        /// settings for this object. When possible, even if there are properties in an IAdapter, they should
        /// actually get and store these data in the Info object. (Conversely anything that is not from (or derived from the configuration
        /// should not be put in Info.
        /// </summary>
        public IAdapterInfo Info
        {
            get;
            set;
        }

        /// <summary>
        /// The <see cref="INotificationReceiver"/> object for inter-Adapter and other Intra-SIEGate messaging.
        /// </summary>
        protected INotificationReceiver Receiver
        {
            get;
            set;
        }

        /// <summary>
        /// Unique Identifier for this Adapter. May not always be a uint.
        /// </summary>
        public uint ID
        {
            get
            {
                return Info.ID;
            }
        }

        /// <summary>
        /// The set of StreamIDs that this Adapter will process. (Comes from the AdapterInfo instance)
        /// </summary>
        public ISet<Guid> IncomingStreamIDs
        {
            get
            {
                return Info.IncomingStreamIDs;
            }
            protected set
            {
                Info.IncomingStreamIDs = value;
            }
        }

        /// <summary>
        /// The StreamIDs of measurement streams created by this Adapter. 
        /// </summary>
        public ISet<Guid> OutgoingStreamIDs
        {
            get
            {
                return Info.OutgoingStreamIDs;
            }
            protected set
            {
                Info.OutgoingStreamIDs = value;
            }
        }

        /// <summary>
        /// Event for passing measurements to the next step. Other adapters, AdapterContainers, and so on 
        /// hook into this. It is the sole place measurements come out of an adapter.
        /// </summary>
        public MeasurementPasser Emit
        {
            get;
            set;
        }
        #endregion

        #region [ Methods ]

        /// <summary>
        /// Method for bulk handling of measurements. Override if your Adapter implementation can handle multiple measurements at once.
        /// 
        /// Otherwise this will just call HandleMeasurement for each of the available measurements.
        /// </summary>
        /// <param name="measurements">The currently available measurements to handle.</param>
        protected virtual void HandleMeasurements(IEnumerable<IMeasurement> measurements)
        {
            long i = 0;
            foreach (IMeasurement m in measurements)
            {
                HandleMeasurement(m);
                i++;
            }
            m_nrecv.Add(i);
        }

        /// <summary>
        /// Collect is the method called by external parties (other adapters, AdapterContainers, etc) to pass measurements to 
        /// and Adapter for processing. This method has the appropriate signature for plugging with Emit.
        /// </summary>
        /// <param name="measurements"></param>
        public void Collect(params IMeasurement[] measurements)
        {
            // Todo: Make sealed? Make Internal?
            IEnumerable<IMeasurement> filteredMeasurements = measurements.Where(m => IncomingStreamIDs.Contains(m.StreamID));
            HandleMeasurements(filteredMeasurements);
        }

        /// <summary>
        /// Convenience method for pluggin adapters together, e.g. within an AdapterContainer. Will make the other adapter, which
        /// is passed as an argument, the downstream adapter. Will throw an exception if the downstream container does not want any
        /// measurements created by this adapter.
        /// </summary>
        /// <param name="other">The downstream adapter.</param>
        public virtual void link(IAdapter other)
        {
            // Todo: Make sealed, make internal
            // Check that there is at least one StreamID present in the outgoing set that is also in the incoming set.
            // Otherwise the next adapter is incompatible with the next adapter.
            if (other.IncomingStreamIDs.Intersect(OutgoingStreamIDs).Any())
            {
                Emit += other.Collect;
            }
            else
            {
                throw new Exception("Cannot connect those adaptors because of measurement compatibility!");
            }
        }

        /// <summary>
        /// Fires the Emit event for this adapter. This method should be called when and adapter has measurements available to pass on
        /// to the next adapter in the processing chain
        /// </summary>
        /// <param name="m">The measurments available for passing.</param>
        public void OnEmit(params IMeasurement[] m)
        {
            m_nsent.Add(m.Length);
            if ((object)Emit != null)
            {
                Emit(m);
            }
        }

        // todo: put a way to do collect and handle with a list of measurements as a param to handle... 
        /// <summary>
        /// This method must be implemented by extension classes. It does the bulk of the work for this adapter, and does the actual 
        /// work of the adapter. If you want to handle many measurements at once (e.g. in a loop), override the HandleMeasurements method, but
        /// you'll still need to stub this out.
        /// </summary>
        /// <param name="m">The available measurement.</param>
        protected abstract void HandleMeasurement(IMeasurement m);

        #endregion
    }
}
